#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
#include "ruby-m3g-internal.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct AlignmentAccessor {
        Node* node;
    };
    struct TargetReferenceAccessor {
        Node* node;
        int   axis;
    };
    VALUE rb_cNode_AlignmentAccessor;
    VALUE rb_cNode_TargetReferenceAccessor;
}

void ruby_Node_mark (Node* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_Node_free (Node* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~Node ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_Node_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(Node));
    return Data_Wrap_Struct (self, ruby_Node_mark, ruby_Node_free, p);
}

VALUE ruby_Node_initialize (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);
    __TRY__;
    new (p) Node;
    __CATCH__;
    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_Node_align (VALUE self, VALUE val_reference)
{
    if (val_reference != Qnil && !rb_obj_is_kind_of(val_reference, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (epected M3G::Node or nil)");
    }
    Node* p;
    Data_Get_Struct (self, Node, p);
    Node* ref = NULL;
    if (val_reference != Qnil) {
        Data_Get_Struct (val_reference, Node, ref);
    }
    __TRY__;
    p->align (ref);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Node_get_alignment (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);
    AlignmentAccessor* accessor;
    VALUE          val_accessor = Data_Make_Struct (rb_cNode_AlignmentAccessor, AlignmentAccessor, 0, -1, accessor);
    accessor->node = p;
    return val_accessor;
}


VALUE ruby_Node_get_alpha_factor (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);

    float alpha;
    __TRY__;
    alpha = p->getAlphaFactor ();
    __CATCH__;
    return rb_float_new(alpha);    
}

VALUE ruby_Node_get_parent (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);

    Node* parent;
    __TRY__;
    parent = p->getParent ();
    __CATCH__;

    return parent ? (VALUE)parent->getExportedEntity() : Qnil;
}

VALUE ruby_Node_get_scope (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);

    int scope;
    __TRY__;
    scope = p->getScope();
    __CATCH__;

    return INT2NUM(scope);
}

VALUE ruby_Node_get_transform_to (VALUE self, VALUE val_target)
{
    if (!rb_obj_is_kind_of(val_target, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (epected M3G::Node)");
    }
    Node* p;
    Data_Get_Struct (self, Node, p);
    Node* target;
    Data_Get_Struct (val_target, Node, target);
    Transform* trans;
    VALUE  val_trans = Data_Make_Struct (rb_cTransform, Transform, ruby_Transform_mark, ruby_Transform_free, trans);
    ruby_Transform_initialize (0, NULL, val_trans);
    bool ok;
    __TRY__;
    ok = p->getTransformTo (target, trans);
    __CATCH__;
    VALUE val_ok  = ok ? Qtrue : Qfalse;
    VALUE val_ret = rb_ary_new2 (2);
    rb_ary_push (val_ret, val_ok);
    rb_ary_push (val_ret, val_trans);
    return val_ret;
}

VALUE ruby_Node_is_picking_enabled (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);

    bool enabled;
    __TRY__;
    enabled = p->isPickingEnabled ();
    __CATCH__;
    return enabled ? Qtrue : Qfalse;
}

VALUE ruby_Node_is_rendering_enabled (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);

    bool enabled;
    __TRY__;
    enabled = p->isRenderingEnabled ();
    __CATCH__;
    return enabled ? Qtrue : Qfalse;
}

VALUE ruby_Node_set_alignment (VALUE self, VALUE val_args)
{
    rb_check_type (val_args, T_ARRAY);

    VALUE val_z_ref    = rb_ary_entry (val_args, 0);
    VALUE val_z_target = rb_ary_entry (val_args, 1);
    VALUE val_y_ref    = rb_ary_entry (val_args, 2);
    VALUE val_y_target = rb_ary_entry (val_args, 3);
    Node* p;
    Data_Get_Struct (self, Node, p);
    Node* z_ref, *y_ref;
    Data_Get_Struct (val_z_ref, Node, z_ref);    
    Data_Get_Struct (val_y_ref, Node, y_ref);
    int z_target, y_target;
    z_target = NUM2INT (val_z_target);
    y_target = NUM2INT (val_y_target);
    __TRY__;
    p->setAlignment (z_ref, z_target, y_ref, y_target);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Node_set_alpha_factor (VALUE self, VALUE val_alpha_factor)
{
    Node* p;
    Data_Get_Struct (self, Node, p);
    float alpha = NUM2DBL(val_alpha_factor);
    __TRY__;
    p->setAlphaFactor (alpha);
    __CATCH__;
    return Qnil;    
}

VALUE ruby_Node_set_picking_enable (VALUE self, VALUE val_enable)
{
    if (val_enable != Qtrue && val_enable != Qfalse) {
        rb_raise (rb_eArgError, "wrong argument type (expected bool)");
    }
    Node* p;
    Data_Get_Struct (self, Node, p);
    bool enable = (val_enable == Qtrue) ? true : false;
    __TRY__;
    p->setPickingEnable (enable);
    __CATCH__;
    return Qnil;    
}

VALUE ruby_Node_set_rendering_enable (VALUE self, VALUE val_enable)
{
    if (val_enable != Qtrue && val_enable != Qfalse) {
        rb_raise (rb_eArgError, "wrong argument type (expected bool)");
    }
    Node* p;
    Data_Get_Struct (self, Node, p);
    bool  enable = (val_enable == Qtrue) ? true : false;
    __TRY__;
    p->setRenderingEnable (enable);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Node_set_scope (VALUE self, VALUE val_scope)
{
    Node* p;
    Data_Get_Struct (self, Node, p);
    int scope = NUM2INT (val_scope);
    __TRY__;
    p->setScope (scope);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Node_print (VALUE self)
{
    Node* p;
    Data_Get_Struct (self, Node, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 * Node_AlignmentAccessor
 */

VALUE ruby_Node_AlignmentAccessor_get_alignment (VALUE self, VALUE val_axis)
{
    AlignmentAccessor* p;
    Data_Get_Struct (self, AlignmentAccessor, p);
    TargetReferenceAccessor* accessor;
    VALUE                val_accessor = Data_Make_Struct (rb_cNode_TargetReferenceAccessor, TargetReferenceAccessor, 0, -1, accessor);
    accessor->node = p->node;
    accessor->axis = NUM2INT(val_axis);
    return val_accessor;
}

/**
 * Node::TargetReferenceAccessor
 */
VALUE ruby_Node_TargetReferenceAccessor_get_target (VALUE self)
{
    TargetReferenceAccessor* p;
    Data_Get_Struct (self, TargetReferenceAccessor, p);
    int target;
    __TRY__;
    target = p->node->getAlignmentTarget (p->axis);
    __CATCH__;
    return INT2NUM(target);
}

VALUE ruby_Node_TargetReferenceAccessor_get_reference (VALUE self)
{
    TargetReferenceAccessor* p;
    Data_Get_Struct (self, TargetReferenceAccessor, p);
    Node* ref;
    __TRY__;
    ref = p->node->getAlignmentReference (p->axis);
    __CATCH__;
    return ref ? (VALUE)ref->getExportedEntity() : Qnil;
}



void register_Node ()
{
    // Node
    rb_cNode                = rb_define_class_under (rb_mM3G, "Node",                rb_cTransformable);

    rb_define_const (rb_cNode, "NONE",   INT2NUM(Node::NONE));
    rb_define_const (rb_cNode, "ORIGIN", INT2NUM(Node::ORIGIN));
    rb_define_const (rb_cNode, "X_AXIS", INT2NUM(Node::X_AXIS));
    rb_define_const (rb_cNode, "Y_AXIS", INT2NUM(Node::Y_AXIS));
    rb_define_const (rb_cNode, "Z_AXIS", INT2NUM(Node::Z_AXIS));

    rb_define_alloc_func (rb_cNode, ruby_Node_allocate);
    rb_define_private_method (rb_cNode, "initialize",  (VALUE(*)(...))ruby_Node_initialize, 0);

    rb_define_method (rb_cNode, "align",               (VALUE(*)(...))ruby_Node_align,                1);
    rb_define_method (rb_cNode, "alignment",           (VALUE(*)(...))ruby_Node_get_alignment,        0);
    rb_define_method (rb_cNode, "alpha_factor",        (VALUE(*)(...))ruby_Node_get_alpha_factor,     0);
    rb_define_method (rb_cNode, "parent",              (VALUE(*)(...))ruby_Node_get_parent,           0);
    rb_define_method (rb_cNode, "scope",               (VALUE(*)(...))ruby_Node_get_scope,            0);
    rb_define_method (rb_cNode, "transform_to",        (VALUE(*)(...))ruby_Node_get_transform_to,     1);
    rb_define_method (rb_cNode, "picking_enabled?",    (VALUE(*)(...))ruby_Node_is_picking_enabled,   0);
    rb_define_method (rb_cNode, "rendering_enabled?",  (VALUE(*)(...))ruby_Node_is_rendering_enabled, 0);
    rb_define_method (rb_cNode, "alignment=",          (VALUE(*)(...))ruby_Node_set_alignment,        1);
    rb_define_method (rb_cNode, "alpha_factor=",       (VALUE(*)(...))ruby_Node_set_alpha_factor,     1);
    rb_define_method (rb_cNode, "picking_enable=",     (VALUE(*)(...))ruby_Node_set_picking_enable,   1);
    rb_define_method (rb_cNode, "rendering_enable=",   (VALUE(*)(...))ruby_Node_set_rendering_enable, 1);
    rb_define_method (rb_cNode, "scope=",              (VALUE(*)(...))ruby_Node_set_scope,            1);
    rb_define_method (rb_cNode, "to_s",                (VALUE(*)(...))ruby_Node_print,                0);

    // Node::AlignmentAccessor
    rb_cNode_AlignmentAccessor  = rb_define_class_under (rb_cNode, "AlignmentAccessor", rb_cObject);

    rb_define_method (rb_cNode_AlignmentAccessor, "[]",        (VALUE(*)(...))ruby_Node_AlignmentAccessor_get_alignment,    1);

    // Node::TargetReferenceAccessor
    rb_cNode_TargetReferenceAccessor  = rb_define_class_under (rb_cNode, "TargetReferenceAccessor", rb_cObject);

    rb_define_method (rb_cNode_TargetReferenceAccessor, "target",        (VALUE(*)(...))ruby_Node_TargetReferenceAccessor_get_target,    0);
    rb_define_method (rb_cNode_TargetReferenceAccessor, "reference",     (VALUE(*)(...))ruby_Node_TargetReferenceAccessor_get_reference, 0);
}
